/******************************************************************************
 * The Unofficial IRC API for C                                               *
 * Started 12-5-2005                                                          *
 * Not Completed (PRE-ALPHA WORK IN PROGRESS)                                 *
 * Executive Author: Dustin Molieri dmolieri@users.sourceforge.net            *
 * E-Mail the author if you would like to contribute your expertise.          *
 *                                                                            *
 * DESCRIPTION                                                                *
 *                                                                            *
 * Unofficial C API for RFC1459 client to server communication using IRC      *
 * protocol. Designed for developers who would like to integrate IRC          *
 * communication into their application without knowledge of the IRC          *
 * protocol. This API was designed to be as easy to use as the MySQL API      *
 * would be for connecting to a SQL server. This API gives developers the     *
 * ability to build a complete IRC client with little to no understanding of  *
 * the IRC protocol.                                                          *
 *                                                                            *
 * This project is licensed using the GNU Public License Agreement (GPL)      *
 ******************************************************************************/
 
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <circapi.h>

char err_buff[SMALLBUFF];			// buffer for storing error messages (if any)

/******************
 * Root Functions *
 ******************/

int irc_connect(int *sockfd, char *srv, char *port, char *nick, char *user, unsigned char invisible, char *name, int options, ...)
{
	struct hostent *h;
	struct sockaddr_in server_address;
	va_list ap;
	int ct, x;
	char nick_msg[SMALLBUFF], user_msg[SMALLBUFF];
	char buff[BIGBUFF];
	int flag=0;
	
	
	irc_precall();

	sprintf(nick_msg, "NICK %s\r\n", nick);
	sprintf(user_msg, "USER %s %c *  :%s\r\n", user, invisible ? '8' : '0', name);
	
	va_start(ap, options);
	
	*sockfd = socket(PF_INET, SOCK_STREAM, 0);
	h=gethostbyname(srv);
	
    server_address.sin_family = AF_INET;    // host byte order 
    server_address.sin_port = htons(atoi(port));  // short, network byte order 
    server_address.sin_addr = *((struct in_addr *)h->h_addr);
    memset(&(server_address.sin_zero), '\0', 8);  // zero the rest of the struct 
    
	if(connect(*sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1)
	{
		switch(errno)
		{
			case EBADF:
				strcpy(err_buff, "EBADF");
				break;
			case EFAULT:
				strcpy(err_buff, "EFAULT");
				break;
			case ENOTSOCK:
				strcpy(err_buff, "ENOTSOCK");
				break;
			case EISCONN:
				strcpy(err_buff, "EISCONN");
				break;
			case ECONNREFUSED:
				strcpy(err_buff, "ECONNREFUSED");
				break;
			case ETIMEDOUT:
				strcpy(err_buff, "ETIMEDOUT");
				break;
			case ENETUNREACH:
				strcpy(err_buff, "ENETREACH");
				break;
			case EADDRINUSE:
				strcpy(err_buff, "EADDRINUSE");
				break;
			case EINPROGRESS:
				strcpy(err_buff, "EINPROGRESS");
				break;
			case EALREADY:
				strcpy(err_buff, "EALREADY");
				break;
			case EAGAIN:
				strcpy(err_buff, "EAGAIN");
				break;
			case EAFNOSUPPORT:
				strcpy(err_buff, "EAFNOSUPPORT");
				break;
			case EACCES:
				strcpy(err_buff, "EACCES");
				break;
			case EPERM:
				strcpy(err_buff, "EPERM");
				break;
			default:
				strcpy(err_buff, "Unknown");
				break;
		}
		return 0;
	}
	nonblocking(sockfd);
	if(irc_send_server_text(sockfd, nick_msg) <= 0)
	{
		close(*sockfd);
		return 0;
	}
	if(irc_send_server_text(sockfd, user_msg) <= 0)
	{
		close(*sockfd);
		return 0;
	}
	return 1;
}

int irc_disconnect(int *sockfd)
{
	irc_precall();
	close(*sockfd);
}

int irc_send_server_text(int *sockfd, char *text)
{
	int bytes=0;
	
	irc_precall();
	
	if((bytes = send(*sockfd, text, strlen(text), 0)) <= 0)
	{
		if(bytes == 0) return 0;
		switch(errno)
		{
			case EAGAIN:
				strcpy(err_buff, "EAGAIN");
				break;
			case EBADF:
				strcpy(err_buff, "EBADF");
				break;
			case ECONNRESET:
				strcpy(err_buff, "ECONNRESET");
				break;
			case EDESTADDRREQ:
				strcpy(err_buff, "EDESTADDRREQ");
				break;
			case EFAULT:
				strcpy(err_buff, "ECONNRESET");
				break;
			case EINTR:
				strcpy(err_buff, "EINTR");
				break;
			case EINVAL:
				strcpy(err_buff, "EINVAL");
				break;
			case EISCONN:
				strcpy(err_buff, "EISCONN");
				break;
			case EMSGSIZE:
				strcpy(err_buff, "EMSGSIZE");
				break;
			case ENOBUFS:
				strcpy(err_buff, "ENOBUFS");
				break;
			case ENOMEM:
				strcpy(err_buff, "ENOMEM");
				break;
			case ENOTCONN:
				strcpy(err_buff, "ENOTCONN");
				break;
			case ENOTSOCK:
				strcpy(err_buff, "ENOTSOCK");
				break;
			case EOPNOTSUPP:
				strcpy(err_buff, "EOPNOTSUPP");
				break;
			case EPIPE:
				strcpy(err_buff, "EPIPE");
				break;
			default:
				strcpy(err_buff, "UNKNOWNSENDERR");
				break;
		}
		return 0;
	}
	return bytes;
}

int irc_get_server_text(int *sockfd, char *buff, int buff_len)
{
	int bytes=0;
	irc_precall();
	if((bytes = recv(*sockfd, buff, buff_len, 0)) <= 0)
	{
		buff[0] = '\0';
		if(bytes == 0) return 0;
		switch(errno)
		{
			case EAGAIN:
				strcpy(err_buff, "EAGAIN");
				break;
			case EBADF:
				strcpy(err_buff, "EBADF");
				break;
			case EFAULT:
				strcpy(err_buff, "EFAULT");
				break;
			case EINTR:
				strcpy(err_buff, "EINTR");
				break;
			case EINVAL:
				strcpy(err_buff, "EINVAL");
				break;
			case ENOMEM:
				strcpy(err_buff, "ENOMEM");
				break;
			case ENOTCONN:
				strcpy(err_buff, "ENOTCONN");
				break;
			case ENOTSOCK:
				strcpy(err_buff, "ENOTSOCK");
				break;
			default:
				strcpy(err_buff, "UNKNOWNSENDERR");
				break;
		}
		return NO_TEXT;
	}
	return bytes;
}
												
/*********************
 * SubRoot Functions *
 *********************/
												
int irc_privmsg(int *sockfd, char *dest, char *message)
{
	char buff[SMALLBUFF];
	
	memset(buff, '\0', sizeof(buff));
	if(strlen(dest)+strlen(message) > SMALLBUFF+16) return 0;
	
	sprintf(buff, "PRIVMSG %s :%s\r\n", dest, message);
	return irc_send_server_text(sockfd, buff);
}
												
int irc_send_user_text(int *sockfd, char *nick, char *text)
{
	if(nick[0] == '#' || nick[0] == '&') return 0;
	return irc_privmsg(sockfd, nick, text);
}

int irc_send_channel_text(int *sockfd, char *channel, char *text)
{
	if(channel[0] != '#' && *channel != '&') return 0;
	return irc_privmsg(sockfd, channel, text);
}

/******************************************************
 * These functions use root functions for interaction *
 ******************************************************/												
 
int irc_join_channel(int *sockfd, char *channel)
{
	char buff[SMALLBUFF];
	irc_precall();
	sprintf(buff, "JOIN %s\r\n", channel);
	return irc_send_server_text(sockfd, buff);
}

int irc_leave_channel(int *sockfd, char *channel)
{
	char buff[SMALLBUFF];
	irc_precall();
	sprintf(buff, "PART %s\r\n", channel);
	return irc_send_server_text(sockfd, buff);
}

int irc_change_nick(int *sockfd, char *nick)
{
	char buff[SMALLBUFF];
	irc_precall();
	sprintf(buff, "NICK %s\r\n", nick);
	return irc_send_server_text(sockfd, buff);
}

int irc_message_user(int *sockfd, char *nick, char *msg)
{
	char buff[SMALLBUFF];
	irc_precall();
	sprintf(buff, "PRIVMSG %s :%s\r\n", nick, msg);
	return irc_send_server_text(sockfd, buff);
}

int irc_message_channel(int *sockfd, char *channel, char *msg)
{
	irc_precall();
	char buff[SMALLBUFF];
	sprintf(buff, "PRIVMSG %s :%s\r\n", channel, msg);
	return irc_send_server_text(sockfd, buff);
}

int irc_set_topic(int *sockfd, char *channel, char *topic)
{
	irc_precall();
	char buff[SMALLBUFF];
	sprintf(buff, "TOPIC %s :%s\r\n", channel, topic);
	return irc_send_server_text(sockfd, buff);
}

int irc_give_voice(int *sockfd, char *nick, char *channel)
{
	irc_precall();
	char buff[SMALLBUFF];
	sprintf(buff, "MODE %s +v %s\r\n", channel, nick);
	return irc_send_server_text(sockfd, buff);
}

int irc_give_op(int *sockfd, char *nick, char *channel)
{
	irc_precall();
	char buff[SMALLBUFF];
	sprintf(buff, "MODE %s +o %s\r\n", channel, nick);
	return irc_send_server_text(sockfd, buff);
}

int irc_nick_list(int *sockfd, char *channel)
{
	irc_precall();
	char buff[SMALLBUFF];
	sprintf(buff, "NAMES %s\r\n", channel);
	return irc_send_server_text(sockfd, buff);
}


int irc_ircofs_init(struct ircofs *ofs)
{
	memset(ofs, '\0', sizeof(struct ircofs));
	return 1;
}

int irc_buff2ircofs(struct ircofs *ofs, char *buff)
{
	int x, y, z;
	
	// count how many lines are in a buffer
	for(x=y=0; buff[x] != '\0'; ++x)
	{
		if(buff[x] == '\n' || buff[x] == '\0') ++y;
	}
	if((ofs->line_ct = y) < 1) return 0;
	
	#ifdef DEBUG
	printf("WE HAVE %d LINES OF TEXT!\n", ofs->line_ct);
	#endif

	// allocate memory for each line
	ofs->ln = malloc(ofs->line_ct * sizeof(char *)+1);
	for(y=0; y < ofs->line_ct; ++y)
	{
		ofs->ln[y] = (char *)malloc(SMALLBUFF);
		memset(ofs->ln[y], '\0', SMALLBUFF);
		#ifdef DEBUG
		printf("ALLOCATED MEMORY FOR LINE %d\n", y);
		#endif
	}
	
	// store lines from buff into ofs as individual '\0' terminated strings
	// since I memset the strings to '\0' I shouldn't have to null terminate right? ok...
	for(x=y=z=0; buff[z] != '\0' && x < ofs->line_ct;)
	{
		if(buff[z] != '\n' && buff[z] != '\r' && buff[z] != '\0')
		{
			ofs->ln[x][y] = buff[z];
			++y;
			
			// concatinate destination buffer, no line should be >= SMALLBUFF
			if(y >= SMALLBUFF-1) while(buff[z] != '\n' && buff[z] != '\0') ++z;
			else ++z;
		}
		else
		{
			++x;
			
			// concatinate lines to be stored if more than MAX_LINES (safeguard)
			if(x >= ofs->line_ct) while(buff[z] != '\0') ++z;
			else
			{
				while(buff[z] == '\r' || buff[z] == '\n') ++z;
				y=0;
			}
		}
	}

	#ifdef DEBUG
	for(x=0; x<ofs->line_ct; ++x)
	{
		printf("ofs->ln[%d][0]: [%c]\nofs->ln[%d]: [%s]\n", x, ofs->ln[x][0], x, ofs->ln[x]);
	}
	#endif
	
	return ofs->line_ct;
}

int irc_clear_ircofs(struct ircofs *ofs)
{
	int x;
	
	// frees memory allocated for this structure
	for(x=0; x<ofs->line_ct; ++x)
	{
		free(ofs->ln[x]);
	}
	
	// frees memory allocated for pointers
	free(ofs->ln);
	
	// reinitializes the structure
	irc_ircofs_init(ofs);
	
	return 1;
}

/***************************************
 * Set Nonblocking                     *
 ***************************************/
void nonblocking(int *sockfd)
{
	irc_precall();
	fcntl(*sockfd, F_SETFL, O_NONBLOCK);
	return;
}

void irc_precall()
{
	memset(err_buff, '\0', sizeof(err_buff));
	return;
}

char *irc_error()
{
	if(err_buff[0] != '\0') return err_buff;
	strcpy(err_buff, "Unknown Error");
	return err_buff;
}

char *ping_message_index_ping(char *pingmsg)
{
	char *p=pingmsg;
	if(comparen(p, "PING :", 6))
	{
		return p + 6;
	}
	return NULL;
}

unsigned char irc_is_ping(char *ln)
{
	if(ping_message_index_ping(ln) != NULL) return 1;
	return 0;
}

char *channel_message_index_channel(char *chanmsg)
{
	char *p;
	int flag=0;
	
	p = chanmsg;	
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0')
	{
		if(*p == '!') ++flag;
		if(*p == '@') ++flag;
		++p;
	}
	if(flag < 2) return NULL;

	while(*p != ' ' && *p != '\0') ++p;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(!comparen(p, "PRIVMSG", 7)) return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p == '#' || *p == '&') return p;
	return NULL;
}

unsigned char irc_is_channel_message(char *ln)
{
	if(channel_message_index_channel(ln) != NULL) return 1;
	return 0;
}

char *user_message_index_nick(char *usermsg)
{
	char *p;
	int flag=0;
	
	p = usermsg;	
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0')
	{
		if(*p == '!') ++flag;
		if(*p == '@') ++flag;
		++p;
	}
	if(flag < 2) return NULL;
	
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(!comparen(p, "PRIVMSG", 7)) return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p != '#' && *p != '&' && *p != '\0' && *p != ':') return p;
	return NULL;	
}

unsigned char irc_is_user_message(char *ln)
{
	if(user_message_index_nick(ln) != NULL) return 1;
	return 0;
}

char *irc_mode_message_index_mode(char *modemsg)
{
	char *p;
	int flag=0;
	
	p = modemsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0')
	{
		if(*p == '!') ++flag;
		if(*p == '@') ++flag;
		++p;
	}
	if(flag < 2) return NULL;
	
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(comparen(p, "MODE ", 5)) return p + 5;
	return NULL;
}

unsigned char irc_is_mode_message(char *ln)
{
	if(irc_mode_message_index_mode(ln) != NULL) return 1;
	return 0;
}

char *disconnect_message_index_reason(char *discmsg)
{
	char *p;

	p=discmsg;
	if(comparen(p, "ERROR", 5))
	{
		while(*p != ':' && *p != '\0') ++p;
		if(*p == '\0') return NULL;
		if(comparen(p, ":Closing Link:", 14)) return p+14;
	}
	return NULL;
}

unsigned char irc_is_disconnect_message(char *ln)
{
	if(disconnect_message_index_reason(ln) != NULL) return 1;
	return 0;
}

char *join_message_index_nick(char *joinmsg)
{
	char *p;
	p=joinmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen("JOIN", p, 4)) return joinmsg+1;
	return NULL;
}

char *join_message_index_channel(char *joinmsg)
{
	char *p;
	p=joinmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen("JOIN ", p, 5)) return p+5;
	return NULL;
}

unsigned char irc_is_join_message(char *ln)
{
	if(join_message_index_nick(ln) != NULL) return 1;
	return 0;
}

char *part_message_index_nick(char *partmsg)
{
	char *p;
	p=partmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen(p, "PART ", 5)) return partmsg+1;
	return NULL;
}

char *part_message_index_channel(char *partmsg)
{
	char *p;
	p=partmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen(p, "PART ", 5)) return p+6;
	return NULL;
}

unsigned char irc_is_part_message(char *ln)
{
	if(part_message_index_nick(ln) != NULL) return 1;
	return 0;
}

char *quit_message_index_nick(char *quitmsg)
{
	char *p;
	p=quitmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen(p, "QUIT", 4)) return quitmsg+1;
	return NULL;
}

char *quit_message_index_reason(char *quitmsg)
{
	char *p;
	p=quitmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return NULL;
	if(comparen(p, "QUIT ", 5)) return p+5;
	return NULL;
}

unsigned char irc_is_quit_message(char *ln)
{
	if(quit_message_index_nick(ln) != NULL) return 1;
	return 0;
}

char *nick_list_index_first_nick(char *nicklistmsg)
{
	char *p;
	p=nicklistmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	else return NULL;
	if(*p == '\0') return 0;
	if(comparen(p, "353 ", 4))
	{
		while(*p != ':' && *p != '\0') ++p;
		while(*p == ' ') ++p;
		if(*p != '\0' && *(++p) != '\0') return p;
		else return NULL;
	}
	return NULL;	
}

char *nick_list_index_channel(char *nicklistmsg)
{
	char *p;
	p=nicklistmsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p != ' ') return NULL;
	++p;
	if(*p == '\0') return NULL;
	if(comparen(p, "353 ", 4))
	{
		while(*p != '#' && *p != '&' && *p != '\0') ++p;
		if(*p != '#' && *p != '&') return NULL;
		return p;
	}
	return NULL;
}


unsigned char irc_is_nick_list(char *ln)
{
	if(nick_list_index_first_nick(ln) != NULL) return 1;
	return 0;
}

unsigned char irc_is_nick_list_complete(char *ln)
{
	if(rpl_index_message("366", ln) == NULL) return 0;
	else return 1;
}

char *join_reject_index_channel(char *joinrejectmsg)
{
	int chan=0;
	int ref=0;
	if(joinrejectmsg[0] != ':') return NULL;
	++ref;
	while(joinrejectmsg[ref] != ':' && joinrejectmsg[ref] != '\0')
	{
		if(joinrejectmsg[ref] == '#' || joinrejectmsg[ref] == '&') chan=ref;
		++ref;
	}
	if(joinrejectmsg[ref] == '\0' || joinrejectmsg[ref] == '\0') return NULL;
	if(comparen(joinrejectmsg+ref, ":Cannot join channel ", 21))
	{
		return joinrejectmsg+chan;
	}
	return NULL;
}

unsigned char irc_is_join_reject(char *ln)
{
	if(join_reject_index_channel(ln) != NULL) return 1;
	return 0;
}

int irc_user_nickchange(char *nickchangemsg, struct t_channels *ircch)
{
	char oldnick[48], newnick[48], *d, *p;
	unsigned int x, y, retval;
	
	if((p=nick_change_index_newnick(nickchangemsg)) == NULL) return 0;
	d=newnick;
	while(*p != ' ' && *p != '\0') *(d++) = *(p++);
	*d = '\0';
	if((p=nick_change_index_oldnick(nickchangemsg)) == NULL) return 0;
	d=oldnick;
	while(*p != '!' && *p != '\0') *(d++) = *(p++);
	*d = '\0';
	retval=0;
	for(x=0; x<ircch->field_ct; ++x)
	{
		for(y=0; y<ircch->channels[x].user_field_ct; ++y)
		{
			if(compare(ircch->channels[x].users[y].nick, oldnick))
			{
				strcpy(ircch->channels[x].users[y].nick, newnick);
				++retval;
			}
		}
	}
	if(retval == 0)
	{
		sprintf(err_buff, "irc_user_nickchange: didn't change any nicks from [%s] to [%s]",
			oldnick, newnick);
		return 0;
	}
	return 1;
}

unsigned char irc_is_nick_change(char *ln)
{
	if(nick_change_index_newnick(ln) != NULL) return 1;
	return 0;
}

char *motd_index_message(char *motdmsg)
{
	return rpl_index_message("372", motdmsg);
}

int irc_is_motd_message(char *ln)
{
	if(motd_index_message(ln) != NULL) return 1;
	else return 0;
}

char *motd_end_index_message(char *motdmsg)
{
	return rpl_index_message("376", motdmsg);
}

int irc_is_motd_end_message(char *ln)
{
	if(motd_end_index_message(ln) != NULL) return 1;
	else return 0;
}

char *notice_index_message(char *noticemsg)
{
	char *p=noticemsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p != ' ') return NULL;
	if(!comparen(p, " NOTICE ", 8)) return NULL;
	while(*p != ':' && *p != '\0') ++p;
	if(*p != ':') return NULL;
	return ++p;
}

int irc_is_notice_message(char *ln)
{
	if(notice_index_message(ln) != NULL) return 1;
	else return 0;
}

char *welcome_message_index_message(char *welcomemsg)
{
	return rpl_index_message("001", welcomemsg); 
}

unsigned char irc_is_welcome_message(char *ln)
{
	if(welcome_message_index_message(ln) != NULL) return 1;
	return 0;
}

char *yourhost_message_index_message(char *yourhostmsg)
{
	return rpl_index_message("002", yourhostmsg);
}

unsigned char irc_is_yourhost_message(char *ln)
{
	if(yourhost_message_index_message(ln) != NULL) return 1;
	return 0;
}

char *created_message_index_message(char *createdmsg)
{
	return rpl_index_message("003", createdmsg);
}

unsigned char irc_is_created_message(char *ln)
{
	if(created_message_index_message(ln) != NULL) return 1;
	return 0;
}

char *myinfo_message_index_message(char *myinfomsg)
{
	return rpl_index_message("004", myinfomsg);
}

unsigned char irc_is_myinfo_message(char *ln)
{
	if(myinfo_message_index_message(ln) != NULL) return 1;
	return 0;
}

char *bounce_message_index_message(char *bouncemsg)
{
	return rpl_index_message("005", bouncemsg);
}

unsigned char irc_is_bounce_message(char *ln)
{
	if(bounce_message_index_message(ln) != NULL) return 1;
	return 0;
}

char *notice_auth_message_index_message(char *noticemsg)
{
	if(!comparen(noticemsg, "NOTICE AUTH :", 13)) return NULL;
	return noticemsg+13;
}

unsigned char irc_is_notice_auth_message(char *ln)
{
	if(notice_auth_message_index_message(ln) != NULL) return 1;
	return 0;
}

unsigned int irc_translate(char *ln)
{
	if(irc_is_ping(ln)) return PING_MSG;
	else if(irc_is_channel_message(ln)) return CHANNEL_PRIVMSG_MSG;
	else if(irc_is_user_message(ln)) return USER_PRIVMSG_MSG;
	else if(irc_is_mode_message(ln)) return MODE_CHANGE_MSG;
	else if(irc_is_disconnect_message(ln)) return DISCONNECT_MSG;
	else if(irc_is_join_message(ln)) return USER_JOIN_MSG;
	else if(irc_is_part_message(ln)) return USER_PART_MSG;
	else if(irc_is_quit_message(ln)) return USER_QUIT_MSG;
	else if(irc_is_nick_list(ln)) return CHANNEL_NAMES_MSG;
	else if(irc_is_nick_list_complete(ln)) return CHANNEL_NAMES_COMPLETE_MSG;
	else if(irc_is_join_reject(ln)) return CHANNEL_JOIN_REJECT_MSG;
	else if(irc_is_nick_change(ln)) return USER_NICK_CHANGE_MSG;
	else if(irc_is_motd_message(ln)) return MOTD_MSG;
	else if(irc_is_motd_end_message(ln)) return MOTD_END_MSG;
	else if(irc_is_notice_message(ln)) return NOTICE_MSG;
	else if(irc_is_welcome_message(ln)) return WELCOME_MSG;
	else if(irc_is_yourhost_message(ln)) return YOURHOST_MSG;
	else if(irc_is_created_message(ln)) return CREATED_MSG;
	else if(irc_is_myinfo_message(ln)) return MYINFO_MSG;
	else if(irc_is_bounce_message(ln)) return BOUNCE_MSG;
	else if(irc_is_notice_auth_message(ln)) return NOTICEAUTH_MSG;
	return UNKNOWN_MSG;
}

char *rpl_index_message(char *numeric, char *message)
{
	char *p=message;
	char buf[12];

	if(strlen(numeric) > 3)
	{
		strcpy(err_buff, "not a numeric");
		return NULL;
	}

	sprintf(buf, " %s ", numeric);

	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p != ' ') return NULL;
	if(!comparen(p, buf, 5)) return NULL;
	while(*p != ':' && *p != '\0') ++p;
	if(*p != ':') return NULL;
	return ++p;
}

char *irc_numeric(char *ln, char *dest)
{
	char *p=ln;
	if(*p != ':') return dest=NULL;
	++p;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == '\0' || *(p+1) == '\0') return 0;
	++p;
	if(*p >= '0' && *p <= '9' && *(p+1) >= '0' && *(p+1) <= '9' && *(p+2) >= '0' && *(p+2) <= '9')
	{
		*dest = *p;
		*(dest+1) = *(p+1);
		*(dest+2) = *(p+2);
		*(p+3) = '\0';
		return dest;
	}
	else return NULL;
}

char *irc_sender(char *ln, char *dest)
{
	char *p, *d;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	d=dest;
	while(*p != ' ' && *p != '\0')
	{
		*d = *p;
		++p;
		++d;
	}
	*d = '\0';
	return dest;
}

char *irc_sender_nick(char *ln, char *dest)
{
	char *p, *d;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	d=dest;
	while(*p != '!' && *p != '\0')
	{
		*d = *p;
		++p;
		++d;
	}
	*d = '\0';
	return dest;
}

char *irc_sender_ip(char *ln, char *dest)
{
	char *p, *d;
	int flag=0;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	d=dest;
	while(*p != '@' && *p != '\0') ++p;
	if(*p == '@') ++p;
	while(*p != ' ' && *p != '\0' && flag <= 3)
	{
		if(*p == '-' || *p == '.')
		{
			if(flag < 3) *d = '.';
			++flag;
		}
		else *d = *p;
		++p;
		++d;
	}
	*d = '\0';
	return dest;
}

char *irc_sender_channel(char *ln, char *dest)
{
	char *p, *d;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	d=dest;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p != '\0') while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p == '#' || *p == '&')
	{
		while(*p != ' ' && *p != '\0')
		{
			*d = *p;
			++p;
			++d;
		}
		*d = '\0';
	}
	return dest;
}

inline char *irc_numeric_channel(char *ln, char *dest)
{
	char *p, *d;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	d=dest;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p != '\0') while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p != '\0') while(*p != ' ' && *p != '\0') ++p;
	if(*p == ' ') ++p;
	if(*p == '#' || *p == '&')
	{
		while(*p != ' ' && *p != '\0')
		{
			*d = *p;
			++p;
			++d;
		}
		*d = '\0';
	}
	return dest;
}

char *irc_sender_message(char *ln, char *dest)
{
	char *p;
	char *d;
	
	p=ln;
	if(*p != ':') return NULL;
	++p;
	while(*p != ':' && *p != '\0') ++p;
	if(*p != ':') return NULL;
	++p;
	if(*p == '\0') return NULL;
	d=dest;
	while(*p != '\0')
	{
		*d = *p;
		++p;
		++d;
	}
	*d = '\0';
	return dest;
}

char *irc_ping_responce(char *ln, char *dest)
{
	char *p;
	if((p=ping_message_index_ping(ln)) != NULL)
	{
		sprintf(dest, "PONG :%s\r\n", p);
		return dest;
	}
	return NULL;
}

int irc_create_first_user(struct ircchannel *ch, char *nick)
{
	if(ch->user_field_ct >= 1)
	{
		strcpy(err_buff, "irc_create_first_user: user_field_ct != 0, use recycle/append!");
		return 0;
	}
	
	ch->users = malloc(sizeof(struct ircuser));
	
	if(ch->users == NULL)
	{
		strcpy(err_buff, "irc_create_first_user: Unable to allocate memory");
		return 0;
	}
	
	strcpy(ch->users[0].nick, nick);
	ch->users[0].active = 1;
	ch->user_field_ct = 1;
	ch->user_active_ct = 1;
	return 1;	
}

int irc_append_user(struct ircchannel *ch, char *nick)
{
	int x;
	
	for(x=0; x<ch->user_field_ct; ++x)
	{
		if(ch->users[x].active && compare(ch->users[x].nick,nick))
		{
			sprintf(err_buff, "irc_append_user: %s is already active in the list, cannot activate this user again!", nick);
			return 0;
		}
	}
	
	if(ch->users == NULL || ch->user_field_ct <= 0)
	{
		strcpy(err_buff, "irc_append_user: Use irc_create_first_user first!");
		return 0;
	}

	ch->users = realloc(ch->users, sizeof(struct ircuser) * (ch->user_field_ct+1));
	
	if(ch->users == NULL)
	{
		strcpy(err_buff, "irc_append_user: Unable to reallocate user buffer");
		return 0;
	}
	
	strcpy(ch->users[ch->user_field_ct].nick, nick);
	ch->users[ch->user_field_ct].active = 1;
	ch->user_field_ct++;
	ch->user_active_ct++;
	return ch->user_field_ct;
}

int irc_recycle_user(struct ircchannel *ch, char *nick)
{
	int x;
	
	for(x=0; x<ch->user_field_ct; ++x)
	{
		if(ch->users[x].active && compare(ch->users[x].nick,nick))
		{
			strcpy(err_buff, "irc_recycle_user: this user is active in the list, use irc_user_deactivate first");
			return 0;
		}
	}
	
	if(ch->users == NULL || ch->user_field_ct == 0)
	{
		strcpy(err_buff, "irc_recycle_user: Cannot recycle, use irc_create_first_user");
		return 0;
	}
	
	for(x=0; x<ch->user_field_ct; ++x)
	{
		if(ch->users[x].active == 0)
		{
			ch->users[x].active = 1;
			strcpy(ch->users[x].nick, nick);
			++ch->user_active_ct;
			return x+1;
		}
	}
	return 0;
}

int irc_user_deactivate(struct ircchannel *ch, char *nick)
{
	int x;
	
	if(ch->users == NULL || ch->user_field_ct <= 0 || ch->user_active_ct <= 0)
	{
		strcpy(err_buff, "irc_user_deactivate: no users to scan");
		return 0;
	}
	
	for(x=0; x<ch->user_field_ct; ++x)
	{
		if(ch->users[x].active == 1)
		{
			if(compare(ch->users[x].nick,nick))
			{
				ch->users[x].active = 0;
				--ch->user_active_ct;
				return 1;
			}
		}
	}
	sprintf(err_buff, "irc_user_deactivate: no users in [%s] with nick [%s]", ch->name, nick);
	return 0;
}

int irc_adduser(char *joinmsg, struct t_channels *ircch)
{
	char *c, *n, *d, nick[32];
	
	c=join_message_index_channel(joinmsg);
	n=join_message_index_nick(joinmsg);
	if(c == NULL || n == NULL)
	{
		strcpy(err_buff, "irc_adduser: invalid joinmsg");
		return 0;
	}
	
	d=nick;
	while(*n != '!' && *n != '\0')
	{
		*d = *n;
		++d;
		++n;
	}
	*d = '\0';
	
	return irc_adduser2(nick, c, ircch);
}

int irc_adduser2(char *nick, char *channel, struct t_channels *ircch)
{
	int x, y;
	unsigned char set_op=0, set_voice=0;
	
	if(nick[0] == '+')
	{
		++nick;
		set_voice=1;
	}
	
	if(nick[0] == '@')
	{
		++nick;
		set_op=1;
	}
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(compare(ircch->channels[x].name, channel))
		{
			if(ircch->channels[x].user_field_ct == 0)
			{
				if(!irc_create_first_user(&ircch->channels[x], nick)) return 0;
				goto success;
			}
			else
			{
				if(!irc_recycle_user(&ircch->channels[x], nick))
				{
					if(!irc_append_user(&ircch->channels[x], nick)) return 0;
				}
				goto success;
			}
		}
	}
	sprintf(err_buff, "irc_adduser: Could not find channel %s in given channel list", channel);
	return 0;
	
	success:
	for(y=0; y<ircch->channels[x].user_field_ct; ++y)
	{
		if(compare(ircch->channels[x].users[y].nick,nick))
		{
			// set new user attributes
			ircch->channels[x].users[y].voice=set_voice;
			ircch->channels[x].users[y].op=set_op;
		}
	}
}

int irc_remuser(char *partmsg, struct t_channels *ircch)
{
	char *c, *n, *d, nick[32];

	c=part_message_index_channel(partmsg);
	n=part_message_index_nick(partmsg);
	
	if(c == NULL || n == NULL)
	{
		strcpy(err_buff, "irc_remuser: invalid partmsg");
		return 0;
	}
	
	d=nick;
	while(*n != '!' && *n != '\0')
	{
		*d = *n;
		++n;
		++d;
	}
	*d = '\0';
	
	return irc_remuser2(nick, c, ircch);
}

int irc_remuser2(char *nick, char *channel, struct t_channels *ircch)
{
	int x;
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(compare(ircch->channels[x].name, channel))
		{
			return irc_user_deactivate(&ircch->channels[x], nick);
		}
	}

	sprintf(err_buff, "irc_remuser: unable to find channel %s in channel list", channel);
	return 0;
}

int irc_remuser3(char *partmsg, struct t_channels *ircch)
{
	char *n, *d, nick[32];
	int x, retval=0;

	n=quit_message_index_nick(partmsg);
	
	if(n == NULL)
	{
		strcpy(err_buff, "irc_remuser: invalid partmsg");
		return 0;
	}
	
	d=nick;
	while(*n != '!' && *n != '\0')
	{
		*d = *n;
		++n;
		++d;
	}
	*d = '\0';
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(irc_remuser2(nick, ircch->channels[x].name, ircch)) ++retval;
	}
	if(!retval) strcpy(err_buff, "irc_remuser3: user does not exist in any channels");
	return retval;
}

int irc_addusers(char *nickmsg, struct t_channels *ircch)
{
	char *p, *c, buff[32], buff2[32];
	int x;
	
	p=nickmsg;
	if(*p != ':') goto invalid_nickmsg;
	while(*p != ' ' && *p != '\0') ++p;
	if(*p != ' ') goto invalid_nickmsg;
	++p;
	if(!comparen("353 ", p, 4)) goto invalid_nickmsg;
	while(*p != '@' && *p != '=' && *p != '\0') ++p;
	if(*p != '@' && *p != '=') goto invalid_nickmsg;
	++p;
	if(*p != ' ') goto invalid_nickmsg;
	++p;
	if(*p != '#' && *p != '&') goto invalid_nickmsg;
	
	c=buff;
	while(*p != ' ' && *p != '\0')
	{
		*c = *p;
		++c;
		++p;
	}
	*c = '\0';
	
	if(*p != ' ') goto invalid_nickmsg;
	
	while(*p != ':' && *p != '\0') ++p;
	if(*p != ':') goto invalid_nickmsg;

	++p;
	while(*p != '\0')
	{
		c = buff2;
		if(*p == ' ') ++p;
		while(*p != ' ' && *p != '\0')
		{
			*c = *p;
			++c;
			++p;
		}
		*c = '\0';
	
		if(strlen(buff2) > 0 && strlen(buff) > 0)
		{
			if(!irc_adduser2(buff2, buff, ircch))
			{
				printf("#### UNABLE TO ADD A USER FROM NICKLIST: chan[%s] user[%s] ###\n### %s ###\n",
				buff, buff2, irc_error());
			}
		}
	}
	if(*p == '\0') return 1;
	
	sprintf(err_buff, "irc_addusers: Channel %s is not in the channel list", buff);
	return 0;
	
	invalid_nickmsg:
	strcat(err_buff, "irc_addusers: invalid nickmsg");
	return 0;
}

int irc_addircuser(struct ircchannel *dest, struct ircuser src)
{
	if(dest->user_field_ct == 0)
	{
		dest->users = malloc(sizeof(struct ircuser));
		if(dest->users == NULL)
		{
			strcpy(err_buff, "irc_addircuser: unable to allocate memory");
			return 0;
		}
	}
	else
	{
		dest->users = realloc(dest->users, sizeof(struct ircuser) * (dest->user_field_ct + 1));
		if(dest->users == NULL)
		{
			strcpy(err_buff, "irc_addircuser: unable to reallocate memory");
			return 0;
		}
	}
	
	++dest->user_field_ct;
	
	memcpy(&dest->users[dest->user_field_ct-1], &src, sizeof(struct ircuser));
	
	return dest->user_field_ct;
}

int irc_remircuser(char *nick_name, char *channel_name, struct t_channels *ircch)
{
	int x;
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(compare(ircch->channels[x].name,channel_name))
		{
			return irc_user_deactivate(&ircch->channels[x], nick_name);
		}
	}
	sprintf(err_buff, "irc_remircuser: unable to find channel %s in channel list", channel_name);
	return 0;
}

int irc_channels_cleanup(struct t_channels *ircch)
{
	struct t_channels chbuff;
	int x, y;
	
	memcpy(&chbuff, ircch, sizeof(struct t_channels));
	memset(ircch, '\0', sizeof(struct t_channels));
	
	for(x=0; x<chbuff.field_ct; ++x)
	{
		if(chbuff.channels[x].active)
		{
			if(!irc_create_first_channel(ircch, chbuff.channels[x].name))
			{
				if(!irc_recycle_channel(ircch, chbuff.channels[x].name))
				{
					if(!irc_append_channel(ircch, chbuff.channels[x].name))
					{
						strcpy(err_buff, "irc_channel_sort: couldn't create/recycle/append an active channel");
						// free up unsuccessful t_channels structure and set everything back to how it was
						irc_clear_t_channels(ircch);
						memcpy(ircch, &chbuff, sizeof(struct t_channels));
						return 0;
					}
				}
			}
		}
		printf("chbuff.channels[x].user_field_ct = %d\n", chbuff.channels[x].user_field_ct);
		for(y=0; y<chbuff.channels[x].user_field_ct; ++y)
		{
		printf("chbuff.channels[%d].users[%d].acvtive = %d\n", x, y, chbuff.channels[x].users[y].active);
			if(chbuff.channels[x].users[y].active)
			{
		puts("preparing to copy...");
				if(!irc_create_first_user(ircch->channels, chbuff.channels[x].users[y].nick))
				{
		puts("couldn't create_first, trying to recycle...");
					if(!irc_recycle_user(ircch->channels, chbuff.channels[x].users[y].nick))
					{
		puts("couldn't recycle, trying append...");
						if(!irc_append_user(ircch->channels, chbuff.channels[x].users[y].nick))
						{
		puts("couldn't append, exiting...");
							strcpy(err_buff, "irc_channel_sort: couldn't create/recycle/append an active user");
							// free up unsuccessful t_channels structure and set everything back to how it was
							irc_clear_t_channels(ircch);
							memcpy(ircch, &chbuff, sizeof(struct t_channels));
							return 0;
						}
					}
				}
			}
		}
	}
	return 1;
}

int irc_t_channels_init(struct t_channels *ircch)
{
	memset(ircch, 0, sizeof(struct t_channels));
	return 1;
}

int irc_ircchannel_init(struct ircchannel *ch)
{
	memset(ch, 0, sizeof(struct ircchannel));
	return 1;
}

int irc_ircuser_init(struct ircuser *ircusr)
{
	memset(ircusr, 0, sizeof(struct ircuser));
	return 1;
}

inline int irc_clear_ircuser(struct ircuser *user)
{
	return irc_ircuser_init(user);
}

int irc_clear_ircchannel(struct ircchannel *channel)
{
	int x;
	free(channel);
	irc_ircchannel_init(channel);
	return 1;
}

int irc_clear_t_channels(struct t_channels *ircch)
{
	irc_clear_ircchannel(ircch->channels);
	irc_t_channels_init(ircch);
	return 0;
}

int irc_channel_deactivate(struct t_channels *ircch, char *name)
{
	int x;
	
	if(ircch == NULL || ircch->field_ct <= 0 || ircch->active_ct <= 0)
	{
		strcpy(err_buff, "irc_channel_dactivate: no channels to scan");
		return 0;
	}
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(ircch->channels[x].active)
		{
			if(compare(ircch->channels[x].name,name))
			{
				--ircch->active_ct;
				free(ircch->channels[x].users);
				irc_ircchannel_init(&ircch->channels[x]);
				return 1;
			}
		}
	}
	strcpy(err_buff, "irc_channel_deactivate: no active channels with that name");
	return 0;
}

int irc_create_first_channel(struct t_channels *ircch, char *name)
{
	ircch->channels = malloc(sizeof(struct ircchannel));
	
	if(ircch->channels == NULL)
	{
		strcpy(err_buff, "irc_create_first_channel: Unable to allocate memory");
		return 0;
	}
	
	irc_ircchannel_init(&ircch->channels[0]);
	strcpy(ircch->channels[0].name, name);
	ircch->channels[0].active = 1;
	ircch->field_ct = 1;
	ircch->active_ct = 1;
	return 1;
}

int irc_recycle_channel(struct t_channels *ircch, char *name)
{
	int x;
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(ircch->channels[x].active && compare(ircch->channels[x].name,name))
		{
			strcpy(err_buff, "irc_recycle_channel: this channel is active in the list, use irc_channel_deactivate first");
			return 0;
		}
	}
	
	if(ircch->channels == NULL || ircch->field_ct == 0)
	{
		strcpy(err_buff, "irc_recycle_channel: Cannot recycle, use irc_create_first_channel");
		return 0;
	}
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(ircch->channels[x].active == 0)
		{
			irc_ircchannel_init(&ircch->channels[x]);
			ircch->channels[x].active = 1;
			strcpy(ircch->channels[x].name, name);
			++ircch->active_ct;
			return x+1;
		}
	}
	
	return 0;
}

int irc_append_channel(struct t_channels *ircch, char *name)
{
	int x;
	
	for(x=0; x<ircch->field_ct; ++x)
	{
		if(ircch->channels[x].active && compare(ircch->channels[x].name,name))
		{
			strcpy(err_buff, "irc_append_channel: this channel is active in the list, use irc_channel_deactivate first");
			return 0;
		}
	}
	
	if(ircch->channels == NULL || ircch->field_ct <= 0)
	{
		strcpy(err_buff, "irc_append_channel: Cannot append, use irc_create_first_channel");
		return 0;
	}

	ircch->channels = realloc(ircch->channels, sizeof(struct ircchannel) * (ircch->field_ct+1));
	
	if(ircch->channels == NULL)
	{
		strcpy(err_buff, "irc_append_channel: Unable to reallocate channel buffer");
		return 0;
	}
	
	irc_ircchannel_init(&ircch->channels[ircch->field_ct]);
	strcpy(ircch->channels[ircch->field_ct].name, name);
	ircch->channels[ircch->field_ct].active = 1;
	ircch->field_ct++;
	ircch->active_ct++;
	return ircch->field_ct;
}

char *nick_change_index_oldnick(char *nickchangemsg)
{
	char *p;
	p=nickchangemsg;
	if(*p != ':') return NULL;
	while(*p != ' ' && *p != '\0') ++p;
	if(!comparen(" NICK ",p, 6)) return NULL;
	return nickchangemsg+1;
}

char *nick_change_index_newnick(char *nickchangemsg)
{
	char *p;
	p=nickchangemsg;
	while(*p != ' ' && *p != '\0') ++p;
	if(!comparen(p, " NICK ", 6)) return NULL;
	while(*p != ':' && *p != '\0') ++p;
	if(*p != ':') return NULL;
	return ++p;
}

char *channel_index_text(char *str)
{
	char *p=str;
	
	while(*p != '#' && *p != '&' && *p != '\0') ++p;
	if(*p == '\0') return NULL;
	else return p;
}

int compare(char *str1, char *str2)
{
	int x=0;
	while(str1[x] != '\0' && str2[x] != '\0')
	{
		if(str1[x] != str2[x])
		{
			if(str1[x] >= 'a' && str1[x] <= 'z')
			{
				if(str1[x]-32 != str2[x]) return 0;
			}
			else if(str1[x] >= 'A' && str1[x] <= 'Z')
			{
				if(str1[x]+32 != str2[x]) return 0;
			}
			else return 0;
		}
		++x;
	}
	if(str1[x] == '\0' && str2[x] != '\0' || str1[x] != '\0' && str2[x] == '\0') return 0;
	return 1;
}

int comparen(char *str1, char *str2, int len)
{
	int x=0;
	while(x < len && str1[x] != '\0' && str2[x] != '\0')
	{
		if(str1[x] != str2[x])
		{
			if(str1[x] >= 'a' && str1[x] <= 'z')
			{
				if(str1[x]-32 != str2[x]) return 0;
			}
			else if(str1[x] >= 'A' && str1[x] <= 'Z')
			{
				if(str1[x]+32 != str2[x]) return 0;
			}
			else return 0;
		}
		++x;
	}
	if(x < len) return 0;
	return 1;
}


char *index2string(char *dest, char *src)
{
	int x=0;
	while(src[x] == ' ') ++x;
	while(src[x] != ' ')
	{
		dest[x] = src[x++];
	}
	dest[x] = '\0';
}




